<?php
/**
 * Created by PhpStorm.
 * User: s.utkin
 * Date: 28.08.14
 * Time: 10:49
 */

namespace Pinenut\Queue;

abstract class Dispatcher
{
	const EXTRA_TIME = 5.0;

	protected $workersActionMap = null;

	/**
	 * @param $configClassName
	 * @throws \Exception
	 */
	public function __construct($configClassName) {

		if (!class_exists($configClassName)) {
			throw new \Exception("Class '$configClassName' doesn't exists.", 500);
		}

		/** @var \Pinenut\Config $configClassName */
		$this->workersActionMap = $configClassName::get('workersActionMap');

		if (empty($this->workersActionMap) && !is_array($this->workersActionMap)) {
			throw new \Exception("Array 'workersActionMap' in class '$configClassName' is empty", 500);
		}
	}

	/**
	 * Запуск диспетчера
	 *
	 * @param ProcessingQueue[] $queueItems - массив элементов очереди
	 */
	public function run(array $queueItems)
	{
		# Группируем уведомления по типу
		$groupedList = [];
		foreach ($queueItems as $queueItem) {
			$type = $queueItem->type;

			$workerActionPath = $this->getWorkerActionPath($type);
			# Если в очереди есть элементы с типом, не относящимся к очереди - удаляем такие элементы
			if ($workerActionPath === null) {
				\Log::msg('Removing queue item', $queueItem, \Log::WARN);
				static::putOff($queueItem);
				continue;
			}

			$groupedList[$type][] = $queueItem;
		}

		# Запускаем воркеры и передаем им уведомления пачками
		foreach ($groupedList as $type => $groupedItems) {
			# Получаем путь к классу воркера по типу уведомлений
			$workerAction = $this->getWorkerActionPath($type);

			# Запускаем воркер
			$workerResponse = static::runWorker($workerAction, $groupedItems);

			# Получаем список успешно обработанных уведомлений
			$successList = (array)$workerResponse->successList;

			# Удаляем успешно обработанные уведомления и снимаем блокировку с неуспешно обработанных
			foreach ($queueItems as $queueItem) {
				# Если воркер успешно отработал уведомление - удаляем это уведомление из очереди
				if (in_array((string)$queueItem, $successList)) {
					$queueItem->remove();
				}
				# Иначе - снимаем с уведомления блокировку (попытаемся обработать его в другой раз)
				else {
					$queueItem->pid = 0;
					static::putOff($queueItem);
				}
			}
		}
	}

	/**
	 * Возвращает путь к воркеру, обрабатывающему указанный тип уведомления
	 *
	 * @param $type int
	 * @return string|null
	 */
	public function getWorkerActionPath($type) {

		if (!isset($this->workersActionMap[$type])) {
			return null;
		}

		return $this->workersActionMap[$type];
	}

	/**
	 * Запускает воркер
	 *
	 * @param $workerClassPath - путь к классу воркера
	 * @param $groupedQueueItems - пачка уведомлений, сгруппированных по получателю (если получатель не указан -
	 * уведомления группируются в элементе с индексом 0)
	 * @return \Pinenut\Response
	 */
	protected static function runWorker($workerClassPath, $groupedQueueItems)
	{
		$request = new \Request($workerClassPath, ['items' => $groupedQueueItems], false);
		$response = \Dispatcher::action($request);

		return $response;
	}

	/**
	 * @param ProcessingQueue $queueItem
	 * @param null|int $delay - задердка в микросекундах
	 */
	public static function putOff(ProcessingQueue $queueItem, $delay = null){
		$delay = $delay ? $delay : static::EXTRA_TIME;

		$queueItem->processTime += $delay;
		$queueItem->save();
	}
} 